﻿//-----------------------------------------------------------------------
// <copyright file="GateShiftMembershipProvider.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings

using System;
using System.Web.Security;
using GateShift.Common.Repository;
using GateShift.Services.GameFramework.GameObjects;

#endregion

namespace GateShift.Web.WebFramework
{
    /// <summary>
    /// ASP.NET MembershipProvider for the GateShift Authentication System
    /// </summary>
    public class GateShiftMembershipProvider : MembershipProvider
    {
        #region Properties

        /// <summary>
        /// Indicates whether the membership provider is configured to allow users to retrieve their passwords.
        /// </summary>
        /// <returns>
        /// Returns false
        /// </returns>
        /// <remarks>
        /// This is not allowed for this membership provider
        /// </remarks>
        public override bool EnablePasswordRetrieval
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Indicates whether the membership provider is configured to allow users to reset their passwords.
        /// </summary>
        /// <returns>
        /// true if the membership provider supports password reset; otherwise, false. The default is true.
        /// </returns>
        public override bool EnablePasswordReset
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval.
        /// </summary>
        /// <returns>
        /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true.
        /// </returns>
        public override bool RequiresQuestionAndAnswer
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// The name of the application using the custom membership provider.
        /// </summary>
        /// <returns>
        /// The name of the application using the custom membership provider.
        /// </returns>
        /// <remarks>
        /// This is not supported for this membership provider
        /// </remarks>
        public override string ApplicationName { get; set; }

        /// <summary>
        /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out.
        /// </summary>
        /// <returns>
        /// The number of invalid password or password-answer attempts allowed before the membership user is locked out.
        /// </returns>
        public override int MaxInvalidPasswordAttempts
        {
            get
            {
                return 10; /* TODO: Allow Config */
            }
        }

        /// <summary>
        /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
        /// </summary>
        /// <returns>
        /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
        /// </returns>
        public override int PasswordAttemptWindow
        {
            get
            {
                return 10; /* TODO: Allow Config */
            }
        }

        /// <summary>
        /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
        /// </summary>
        /// <returns>
        /// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true.
        /// </returns>
        /// <remarks>
        /// This is not supported for this membership provider. System requires unique emails
        /// </remarks>
        public override bool RequiresUniqueEmail
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a value indicating the format for storing passwords in the membership data store.
        /// </summary>
        /// <returns>
        /// One of the <see cref="T:System.Web.Security.MembershipPasswordFormat" /> values indicating the format for storing passwords in the data store.
        /// </returns>
        /// <remarks>
        /// ONLY HASHED PASSWORD STORAGE ALLOWED
        /// </remarks>
        public override MembershipPasswordFormat PasswordFormat
        {
            get
            {
                return MembershipPasswordFormat.Hashed;
            }
        }

        /// <summary>
        /// Gets the minimum length required for a password.
        /// </summary>
        /// <returns>
        /// The minimum length required for a password. 
        /// </returns>
        public override int MinRequiredPasswordLength
        {
            get
            {
                return 6; /* TODO: Allow Config */
            }
        }

        /// <summary>
        /// Gets the minimum number of special characters that must be present in a valid password.
        /// </summary>
        /// <returns>
        /// The minimum number of special characters that must be present in a valid password.
        /// </returns>
        public override int MinRequiredNonAlphanumericCharacters
        {
            get
            {
                return 1; /* TODO: Allow Config */
            }
        }

        /// <summary>
        /// Gets the regular expression used to evaluate a password.
        /// </summary>
        /// <returns>
        /// A regular expression used to evaluate a password.
        /// </returns>
        public override string PasswordStrengthRegularExpression
        {
            get
            {
                return string.Empty; /* TODO: Allow Config */
            }
        }

        #endregion

        /// <summary>
        /// Gets a brief, friendly description suitable for display in administrative tools or other user interfaces (UIs).
        /// </summary>
        /// <returns> A brief, friendly description suitable for display in administrative tools or other UIs. </returns>
        public override string Description
        {
            get
            {
                return "Custom MembershipProvider used for GateShift web authentication.";
            }
        }

        /// <summary>
        /// Updates information about a user in the data source.
        /// </summary>
        /// <param name="user">
        /// A <see cref="T:System.Web.Security.MembershipUser" /> object that represents the user to update and the updated information for the user. 
        /// </param>
        public override void UpdateUser(MembershipUser user)
        {
            throw new System.NotImplementedException();
        }

        /// <summary>
        /// Clears a lock so that the membership user can be validated.
        /// </summary>
        /// <returns>
        /// True if the membership user was successfully unlocked; otherwise, false.
        /// </returns>
        /// <param name="userName">
        /// The membership user whose lock status you want to clear.
        /// </param>
        public override bool UnlockUser(string userName)
        {
            Account account = Account.RetrieveByUserName(userName);

            if (account == null)
            {
                return false;
            }

            account.UnlockAccount();
            account.Save();
            return true;
        }

        /// <summary>
        /// Gets the number of users currently accessing the application.
        /// </summary>
        /// <returns>
        /// The number of users currently accessing the application.
        /// </returns>
        public override int GetNumberOfUsersOnline()
        {
            return 0; // TODO: Show this for users on site
        }

        #region Creation

        /// <summary>
        /// Adds a new membership user to the data source.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the information for the newly created user.
        /// </returns>
        /// <param name="username">
        /// The user name for the new user. 
        /// </param>
        /// <param name="password">
        /// The password for the new user. 
        /// </param>
        /// <param name="email">
        /// The e-mail address for the new user.
        /// </param>
        /// <param name="passwordQuestion">
        /// The password question for the new user.
        /// </param>
        /// <param name="passwordAnswer">
        /// The password answer for the new user
        /// </param>
        /// <param name="isApproved">
        /// Whether or not the new user is approved to be validated.
        /// </param>
        /// <param name="providerUserKey">
        /// The unique identifier from the membership data source for the user.
        /// </param>
        /// <param name="status">
        /// A <see cref="T:System.Web.Security.MembershipCreateStatus" /> enumeration value indicating whether the user was created successfully.
        /// </param>
        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            if (Account.EmailAddressExists(email))
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            if (Account.AccountNameExists(username))
            {
                status = MembershipCreateStatus.DuplicateUserName;
                return null;
            }

            if (!Account.IsEmailAddressValid(email))
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }

            if (!Account.IsAccountNameValid(username))
            {
                status = MembershipCreateStatus.InvalidUserName;
                return null;
            }

            if (passwordAnswer.Length > 128)
            {
                status = MembershipCreateStatus.InvalidAnswer;
                return null;
            }

            if (passwordQuestion.Length > 256)
            {
                status = MembershipCreateStatus.InvalidQuestion;
                return null;
            }

            Account account = new Account
                                  {
                                      AccountName = username,
                                      EmailAddress = email,
                                      RecoveryQuestion = passwordQuestion,
                                      RecoveryAnswer = passwordAnswer
                                  };

            account.SetPassword(password);

            account.Save();
            status = MembershipCreateStatus.Success;
            return GetMemberShipUserFromAccount(account);
        }

        #endregion

        #region Retrieval

        /// <summary>
        /// Gets user information from the data source based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the specified user's information from the data source.
        /// </returns>
        /// <param name="providerUserKey">
        /// The unique identifier for the membership user to get information for.
        /// </param>
        /// <param name="userIsOnline">True to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.
        /// </param>
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            return GetMemberShipUserFromAccount(new Account((Identity)providerUserKey));
        }

        /// <summary>
        /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the specified user's information from the data source.
        /// </returns>
        /// <param name="username">
        /// The name of the user to get information for. 
        /// </param>
        /// <param name="userIsOnline">True to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. 
        /// </param>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            Account account = Account.RetrieveByUserName(username);

            return account == null ? null : GetMemberShipUserFromAccount(account);
        }

        /// <summary>
        /// Gets the user name associated with the specified e-mail address.
        /// </summary>
        /// <returns>
        /// The user name associated with the specified e-mail address. If no match is found, return null.
        /// </returns>
        /// <param name="email">
        /// The e-mail address to search for. 
        /// </param>
        public override string GetUserNameByEmail(string email)
        {
            Account account = Account.RetrieveByEmailAddress(email);

            return account != null ? account.AccountName : null;
        }

        #endregion

        #region Not Supported

        /// <summary>
        /// Gets the password for the specified user name from the data source.
        /// </summary>
        /// <returns>
        /// The password for the specified user name.
        /// </returns>
        /// <param name="username">
        /// The user to retrieve the password for. 
        /// </param>
        /// <param name="answer">
        /// The password answer for the user. 
        /// </param>
        public override string GetPassword(string username, string answer)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Removes a user from the membership data source. 
        /// </summary>
        /// <returns>
        /// True if the user was successfully deleted; otherwise, false.
        /// </returns>
        /// <param name="username">
        /// The name of the user to delete.
        /// </param>
        /// <param name="deleteAllRelatedData">True to delete data related to the user from the database; false to leave data related to the user in the database.
        /// </param>
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotSupportedException();
        }

        #endregion

        #region Password Related

        /// <summary>
        /// Processes a request to update the password question and answer for a membership user.
        /// </summary>
        /// <returns>
        /// True if the password question and answer are updated successfully; otherwise, false.
        /// </returns>
        /// <param name="username">
        /// The user to change the password question and answer for. 
        /// </param>
        /// <param name="password">
        /// The password for the specified user. 
        /// </param>
        /// <param name="newPasswordQuestion">
        /// The new password question for the specified user. 
        /// </param>
        /// <param name="newPasswordAnswer">
        /// The new password answer for the specified user. 
        /// </param>
        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            Account account = Account.RetrieveByUserName(username);
            bool returnValue = false;

            if (account == null)
            {
                return false;
            }

            if (account.ValidatePassword(password))
            {
                account.RecoveryQuestion = newPasswordQuestion;
                account.RecoveryAnswer = newPasswordAnswer;
                returnValue = true;
            }

            account.Save();
            return returnValue;
        }

        /// <summary>
        /// Processes a request to update the password for a membership user.
        /// </summary>
        /// <returns>
        /// True if the password was updated successfully; otherwise, false.
        /// </returns>
        /// <param name="username">
        /// The user to update the password for. 
        /// </param>
        /// <param name="oldPassword">
        /// The current password for the specified user. 
        /// </param>
        /// <param name="newPassword">
        /// The new password for the specified user. 
        /// </param>
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            Account account = Account.RetrieveByUserName(username);
            bool returnValue = false;

            if (account == null)
            {
                return false;
            }

            if (account.ValidatePassword(oldPassword))
            {
                account.SetPassword(newPassword);
                returnValue = true;
            }

            account.Save();
            return returnValue;
        }

        /// <summary>
        /// Resets a user's password to a new, automatically generated password.
        /// </summary>
        /// <returns>
        /// The new password for the specified user.
        /// </returns>
        /// <param name="username">
        /// The user to reset the password for. 
        /// </param>
        /// <param name="answer">
        /// The password answer for the specified user. 
        /// </param>
        public override string ResetPassword(string username, string answer)
        {
            Account account = Account.RetrieveByUserName(username);

            if (account == null)
            {
                return null;
            }

            // TODO Make Random
            account.SetPassword("Develop01");
            return "Develop01";
        }

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <returns>
        /// True if the specified username and password are valid; otherwise, false.
        /// </returns>
        /// <param name="username">
        /// The name of the user to validate. 
        /// </param>
        /// <param name="password">
        /// The password for the specified user. 
        /// </param>
        public override bool ValidateUser(string username, string password)
        {
            Account account = Account.RetrieveByUserName(username);
            bool returnValue = false;

            if (account == null)
            {
                return false;
            }

            if (account.ValidatePassword(password))
            {
                account.UpdateLoginDate();
                returnValue = true;
            }

            account.Save();
            return returnValue;
        }

        #endregion

        #region List Retrievals

        /// <summary>
        /// Gets a collection of all the users in the data source in pages of data.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUserCollection" /> collection that contains a page of <paramref name="pageSize" /><see cref="T:System.Web.Security.MembershipUser" /> objects beginning at the page specified by <paramref name="pageIndex" />.
        /// </returns>
        /// <param name="pageIndex">
        /// The index of the page of results to return. <paramref name="pageIndex" /> is zero-based.
        /// </param>
        /// <param name="pageSize">
        /// The size of the page of results to return.
        /// </param>
        /// <param name="totalRecords">
        /// The total number of matched users.
        /// </param>
        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            return new MembershipUserCollection();
        }

        /// <summary>
        /// Gets a collection of membership users where the user name contains the specified user name to match.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUserCollection" /> collection that contains a page of <paramref name="pageSize" /><see cref="T:System.Web.Security.MembershipUser" /> objects beginning at the page specified by <paramref name="pageIndex" />.
        /// </returns>
        /// <param name="usernameToMatch">
        /// The user name to search for.
        /// </param>
        /// <param name="pageIndex">
        /// The index of the page of results to return. <paramref name="pageIndex" /> is zero-based.
        /// </param>
        /// <param name="pageSize">
        /// The size of the page of results to return.
        /// </param>
        /// <param name="totalRecords">
        /// The total number of matched users.
        /// </param>
        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            return new MembershipUserCollection();
        }

        /// <summary>
        /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUserCollection" /> collection that contains a page of <paramref name="pageSize" /><see cref="T:System.Web.Security.MembershipUser" /> objects beginning at the page specified by <paramref name="pageIndex" />.
        /// </returns>
        /// <param name="emailToMatch">
        /// The e-mail address to search for.
        /// </param>
        /// <param name="pageIndex">
        /// The index of the page of results to return. <paramref name="pageIndex" /> is zero-based.
        /// </param>
        /// <param name="pageSize">
        /// The size of the page of results to return.
        /// </param>
        /// <param name="totalRecords">
        /// The total number of matched users.
        /// </param>
        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            return new MembershipUserCollection();
        }

        #endregion

        /// <summary>
        /// Gets a MembershipUser object from the GateShift Account Object
        /// </summary>
        /// <param name="account">GateShift Account to pull data from</param>
        /// <returns>MembershipUser created from the GateShift Account Object</returns>
        private MembershipUser GetMemberShipUserFromAccount(Account account)
        {
            return new MembershipUser(
                Name,
                account.AccountName,
                account.ID,
                account.EmailAddress,
                account.RecoveryQuestion,
                account.Comment,
                true,
                account.Locked,
                account.DateCreated,
                account.DateLastLogin ?? account.DateCreated,
                account.DateLastLogin ?? account.DateCreated,
                account.DateLastPasswordChanged ?? account.DateCreated,
                account.DateLastLocked ?? account.DateCreated);
        }
    }
}
